import projectDir, { readAsync } from 'fs-jetpack';
import Promise from 'bluebird';
import winston from 'winston';
import { extname, basename } from 'path';
import pathToRegexp from 'path-to-regexp';
import parser from 'raml-1-parser';
import { isObject, isNull, isUndefined, isString, get, once, memoize } from 'lodash';

import { dir } from '../../common/utils/fs.js';
import {
    RELOAD_MOCK_SERVER_TRIGGER,
    RELOAD_MOCK_SERVER_FULFILLED,
    RELOAD_MOCK_SERVER_REJECTED,
    SET_SERVER_TIME_TRIGGER,
    SET_SERVER_TIME_FULFILLED
} from '../../../common/constants/mock-server-constants.js';
import { resolve, reset, parseJSON } from './simple-module-loader.js';
import { websocketDispatcher } from '../websocket.js';
import time from '../../model/time.js';

const definitions = new Map();
const pathKeys = [];
const pathPattern = pathToRegexp('/api/:namespace/(.*)', pathKeys);
let state;

class RAMLMatchWarning {
    constructor(message) {
        this.message = message;
    }
}

/**
 * Parse array-like annotations to JSON object
 * @param  {AnnotationRef[]}
 * @return {Object}
 */
function parseAnnotations(annotations) {
    const json = {};

    annotations.forEach(annotation => {
        const attrs = annotation.value().toHighLevel().attrs();
        const key = annotation.name();
        const value = {};

        attrs.forEach(attr => {
            value[attr.name()] = attr.value();
        });

        json[key] = value;
    });

    return json;
}

/**
 * Convert response body array
 * @param  {Body[]} bodies
 * @return {Object}
 */
function bodiesToObject(bodies) {
    const json = {};

    bodies.forEach(body => {
        const key = body.name();
        const examples = body.examples();
        const example = body.example();

        var result = json[key] = {
            default: ''
        };

        // Single example
        if (example) {
            result.default = example.value();
        }

        // Multiple examples
        if (examples) {
            examples.forEach(eg => {
                result[eg.name()] = eg.value();
            });
        }
    });

    return json;
}

/**
 * Match a path to corresponding defined handler
 * or an example object.
 * This function will be CACHED.
 * @param  {string}             path
 * @param  {string}             method
 * @param  {Map<string, API>}   definitions
 * @return {Object|Function}
 */
const matchPath = memoize(async (path, method) => {
    const results = pathPattern.exec(path);

    winston.debug('match', { path, method });

    // 路径不匹配 api 路径要求
    if (isNull(results)) {
        return new RAMLMatchWarning("not an api-related path");
    }

    const [ fullPath, namespace, childPath ] = results;
    const api = definitions.get(namespace);

    // 没有找到对应 swagger 入口文件
    if (isUndefined(api)) {
        return new RAMLMatchWarning("not matching any raml file");
    }

    const resourceUri = `/${childPath}`;

    const matchedResource = api.resources().find(
        item => item.completeRelativeUri() === resourceUri);

    // 找不到相匹配的资源
    if (isUndefined(matchedResource)) {
        return new RAMLMatchWarning("not matching any resource");
    }

    const resourceAnnotations = parseAnnotations(matchedResource.annotations());

    const matchedMethod = matchedResource.methods().find(
        item => item.method() === method);

    // 找不到相匹配的 METHOD
    // 比如只支持 GET 不支持 POST
    if (isUndefined(matchedMethod)) {
        return new RAMLMatchWarning("method not allowed");
    }

    const methodAnnotaions = parseAnnotations(matchedMethod.annotations());
    const matchedResponse = matchedMethod.responses()[0];
    const examples = bodiesToObject(matchedResponse.body());

    // 使用自定义 Controller
    if (isObject(resourceAnnotations['annotations.controller']) &&
        isObject(methodAnnotaions['annotations.operationId'])) {
        return {
            controller: resourceAnnotations['annotations.controller'].value,
            action: methodAnnotaions['annotations.operationId'].value,
            examples
        };
    }

    // 选择第一个出现在 responses 节点中的作为最后 response
    if (matchedMethod.responses().length === 0) {
        throw new Error("no response found");
    }

    return examples;
}, (path, method) => `${method.toUpperCase()} ${path}`);

/**
 * Resolve Response based on request headers
 * @param  {Object}  responses
 * @param  {Context} context
 * @return {Object}
 */
function resolveResponse(responses, context) {
    try {
        if (context.accepts('application/json')) {
            return context.body = parseJSON(
                get(responses, [
                    'application/json'
                ]).default);
        }

        if (context.accepts('text/plain')) {
            return context.body = get(responses, [
                'text/plain'
            ]).default;
        }

        if (context.accepts('text/html')) {
            return context.body = get(responses, [
                'text/html'
            ]).default;
        }

        // 默认按 json 处理
        return context.body = parseJSON(
            get(responses, [
                'application/json'
            ]).default);

    } catch (e) {
        console.error(e);

        context.status = 500;
        context.body = {
            mess: e.message
        };
    }
}

/**
 * 获取当前 Mock Server 状态
 * @return {Action}
 */
export function getState() {
    return state;
}
/**
 * 设置服务器时间
 * @return {Promise}
 */
async function setServerTime(data) {
    // 交易时间内
    time.set(data.time);

    state = {
        type: SET_SERVER_TIME_FULFILLED,
        payload: {
            time: data.time
        }
    };

    websocketDispatcher.dispatch({
                ...state,
                to: 'everyone'
            });
}

/**
 * 重新读取 RAML 文档
 * @return {Promise}
 */
async function reloadRAMLDefinitions() {
    // 读取目录结构
    const tree = await dir('./raml');

    // 清空已有 definitions
    definitions.clear();

    // 清空所有 memoize cache
    matchPath.cache.clear();

    // 重置 Module loader
    reset();

    // 解析 RAML 文档, 仅以根目录文件作为入口文件
    return Promise.map(tree.children,
        (file) => {
            if (file.type === 'file') {
                let path = projectDir.path(`./raml/${file.name}`);
                return parser.loadApi(path)
                    .then(api => {
                        // 使用 baseUri 作为 key 存储 api
                        let key = api.baseUri().value().slice(1);
                        definitions.set(key, api);
                    });
            }

            return Promise.resolve();
        })
        .then(() => {
            state = {
                type: RELOAD_MOCK_SERVER_FULFILLED,
                payload: {
                    size: definitions.size
                }
            };
        }, (err) => {
            state = {
                type: RELOAD_MOCK_SERVER_REJECTED,
                payload: {
                    err: err,
                    message: err.message
                }
            };
        })
        .finally(() => {
            websocketDispatcher.dispatch({
                ...state,
                to: 'everyone'
            });
        });
}

const initialize = once(() => {
    websocketDispatcher.on(RELOAD_MOCK_SERVER_TRIGGER,
        reloadRAMLDefinitions);

    websocketDispatcher.on(SET_SERVER_TIME_TRIGGER,
        setServerTime);

    websocketDispatcher.onNewConnection(conn => {
        websocketDispatcher.dispatch({
            ...getState(),
            to: conn
        })
    });

    reloadRAMLDefinitions();
});

export default function createRAMLMiddleware() {
    // 初始化 RAML 信息 (异步)
    initialize();

    return async function ramlMiddleware(ctx, next) {
        let match;

        try {
            match = await matchPath(ctx.path, ctx.method.toLowerCase());
        } catch (e) {
            console.log(e);
            ctx.status = 500;
            return ctx.body = {
                message: e.message
            };
        }

        // 由其他中间件继续处理请求
        if (isObject(match) &&
            match instanceof RAMLMatchWarning) {
            winston.warn('pass through request:', {
                path: ctx.path,
                reason: match.message
            });
            return await next();
        }

        // 为 Response 添加 `X-ServerTime` 用于服务器客户端时间同步，默认服务器时间
        ctx.set('X-ServerTime', time.get());

        // 有自定义 Controller
        if (isObject(match) &&
            'controller' in match) {
            try {
                let controller = match.controller;
                let action = match.action;
                let examples = match.examples;

                action = resolve(controller)[action];

                return action(ctx, {
                    examples
                });
            } catch (e) {
                console.error(e);

                ctx.status = 500;
                return ctx.body = {
                    mess: `no controller file was matching: "${controller}" -> "${operationId}"`
                };
            }
        }

        resolveResponse(match, ctx);
    }
}
